/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlets.statistics;

import com.google.visualization.datasource.base.TypeMismatchException;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.value.NumberValue;
import com.google.visualization.datasource.datatable.value.Value;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apereo.portal.events.aggr.AggregationInterval;
import org.apereo.portal.events.aggr.BaseAggregationDao;
import org.apereo.portal.events.aggr.BaseAggregationDateTimeComparator;
import org.apereo.portal.events.aggr.groups.AggregatedGroupLookupDao;
import org.apereo.portal.events.aggr.groups.AggregatedGroupMapping;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregation;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationDao;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationDiscriminator;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationDiscriminatorImpl;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationKey;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationKey.ExecutionType;
import org.apereo.portal.events.aggr.portletexec.PortletExecutionAggregationKeyImpl;
import org.apereo.portal.events.aggr.portlets.AggregatedPortletLookupDao;
import org.apereo.portal.events.aggr.portlets.AggregatedPortletMapping;
import org.apereo.portal.events.aggr.portlets.AggregatedPortletMappingNameComparator;
import org.apereo.portal.portlets.statistics.ReportTitleAndColumnDescriptionStrategy.TitleAndCount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.bind.annotation.RenderMapping;
import org.springframework.web.portlet.bind.annotation.ResourceMapping;

/** Portlet Execution reports */
@Controller
@RequestMapping(value = "VIEW")
public class PortletExecutionStatisticsController
        extends BaseStatisticsReportController<
                PortletExecutionAggregation,
                PortletExecutionAggregationKey,
                PortletExecutionAggregationDiscriminator,
                PortletExecutionReportForm> {

    private static final String DATA_TABLE_RESOURCE_ID = "portletExecutionData";
    private static final String REPORT_NAME = "portletExecution.totals";

    @Autowired private ReportTitleAndColumnDescriptionStrategy titleAndColumnDescriptionStrategy;

    @Autowired
    @Qualifier(value = "jpaPortletExecutionAggregationDao")
    private PortletExecutionAggregationDao<PortletExecutionAggregation> portletExecutionDao;

    @Autowired private AggregatedGroupLookupDao aggregatedGroupLookupDao;

    @Autowired private AggregatedPortletLookupDao aggregatedPortletLookupDao;

    public void setTitleAndColumnDescriptionStrategy(
            ReportTitleAndColumnDescriptionStrategy titleAndColumnDescriptionStrategy) {
        this.titleAndColumnDescriptionStrategy = titleAndColumnDescriptionStrategy;
    }

    @RenderMapping(value = "MAXIMIZED", params = "report=" + REPORT_NAME)
    public String getLoginView() throws TypeMismatchException {
        return "jsp/Statistics/reportGraph";
    }

    @ResourceMapping(DATA_TABLE_RESOURCE_ID)
    public ModelAndView renderPortletExecutionAggregationReport(PortletExecutionReportForm form)
            throws TypeMismatchException {
        return renderAggregationReport(form);
    }

    @Override
    protected void initReportForm(PortletExecutionReportForm report) {
        selectFormDefaultPortlet(report);
        selectFormExecutionType(report);
    }

    /** Select the first portlet name by default for the form */
    private void selectFormDefaultPortlet(final PortletExecutionReportForm report) {
        if (!report.getPortlets().isEmpty()) {
            // Portlets already selected, do nothin
            return;
        }

        final Set<AggregatedPortletMapping> portlets = this.getPortlets();
        if (!portlets.isEmpty()) {
            report.getPortlets().add(portlets.iterator().next().getFname());
        }
    }

    /** @return List of Portlets that exist for the aggregation */
    @ModelAttribute("portlets")
    public Set<AggregatedPortletMapping> getPortlets() {
        final Set<AggregatedPortletMapping> groupMappings =
                aggregatedPortletLookupDao.getPortletMappings();

        final Set<AggregatedPortletMapping> sortedGroupMappings =
                new TreeSet<AggregatedPortletMapping>(
                        AggregatedPortletMappingNameComparator.INSTANCE);
        sortedGroupMappings.addAll(groupMappings);
        return sortedGroupMappings;
    }

    @ModelAttribute("executionTypes")
    public ExecutionType[] getExecutionTypes() {
        return ExecutionType.values();
    }

    /** Select the XXXX execution type by default for the form */
    private void selectFormExecutionType(final PortletExecutionReportForm report) {
        if (!report.getExecutionTypeNames().isEmpty()) {
            // Already execution types set, do nothing
            return;
        }

        report.getExecutionTypeNames().add(ExecutionType.RENDER.name());
    }

    @Override
    public String getReportName() {
        return REPORT_NAME;
    }

    @Override
    public String getReportDataResourceId() {
        return DATA_TABLE_RESOURCE_ID;
    }

    @Override
    protected BaseAggregationDao<PortletExecutionAggregation, PortletExecutionAggregationKey>
            getBaseAggregationDao() {
        return this.portletExecutionDao;
    }

    @Override
    protected Set<PortletExecutionAggregationKey> createAggregationsQueryKeyset(
            Set<PortletExecutionAggregationDiscriminator> columnDiscriminators,
            PortletExecutionReportForm form) {
        // Create keys (that exclude the temporal date/time information) from the interval
        // and the data in the column discriminators.
        final AggregationInterval interval = form.getInterval();
        final HashSet<PortletExecutionAggregationKey> keys =
                new HashSet<PortletExecutionAggregationKey>();
        for (PortletExecutionAggregationDiscriminator discriminator : columnDiscriminators) {
            keys.add(
                    new PortletExecutionAggregationKeyImpl(
                            interval,
                            discriminator.getAggregatedGroup(),
                            discriminator.getPortletMapping(),
                            discriminator.getExecutionType()));
        }
        return keys;
    }

    @Override
    protected Comparator<? super PortletExecutionAggregationDiscriminator>
            getDiscriminatorComparator() {
        return PortletExecutionAggregationDiscriminatorImpl.Comparator.INSTANCE;
    }

    @Override
    protected Map<PortletExecutionAggregationDiscriminator, SortedSet<PortletExecutionAggregation>>
            createColumnDiscriminatorMap(PortletExecutionReportForm form) {
        // Collections used to track the queried groups and the results
        final Map<PortletExecutionAggregationDiscriminator, SortedSet<PortletExecutionAggregation>>
                groupedAggregations =
                        new TreeMap<
                                PortletExecutionAggregationDiscriminator,
                                SortedSet<PortletExecutionAggregation>>(
                                PortletExecutionAggregationDiscriminatorImpl.Comparator.INSTANCE);

        // Get concrete group mapping objects that are being queried for
        List<Long> groups = form.getGroups();
        Set<String> portletFNames = form.getPortlets();
        Set<String> executionTypes = form.getExecutionTypeNames();
        for (final Long queryGroupId : groups) {
            AggregatedGroupMapping groupMapping =
                    this.aggregatedGroupLookupDao.getGroupMapping(queryGroupId);
            for (final String portletFName : portletFNames) {
                AggregatedPortletMapping tabMapping =
                        this.aggregatedPortletLookupDao.getMappedPortletForFname(portletFName);
                for (String executionType : executionTypes) {
                    final PortletExecutionAggregationDiscriminator mapping =
                            new PortletExecutionAggregationDiscriminatorImpl(
                                    groupMapping, tabMapping, ExecutionType.valueOf(executionType));
                    // Create the set the aggregations for this report column will be stored in,
                    // sorted chronologically
                    final SortedSet<PortletExecutionAggregation> aggregations =
                            new TreeSet<PortletExecutionAggregation>(
                                    BaseAggregationDateTimeComparator.INSTANCE);

                    // Map the group to the set
                    groupedAggregations.put(mapping, aggregations);
                }
            }
        }

        return groupedAggregations;
    }

    /**
     * Create report title. Criteria that have a single value selected are put into the title.
     * Format and possible options are:
     *
     * <ul>
     *   <li>null (no change needed)
     *   <li>portlet
     *   <li>portlet (execution)
     *   <li>group
     *   <li>group (execution)
     *   <li>execution
     *   <li>portlet - group (also displayed if one of each criteria selected)
     * </ul>
     *
     * @param form the form
     * @return report title
     */
    @Override
    protected String getReportTitleAugmentation(PortletExecutionReportForm form) {
        int groupSize = form.getGroups().size();
        int portletSize = form.getPortlets().size();
        int executionTypeSize = form.getExecutionTypeNames().size();

        // Look up names in case we need them.  They should be in cache so no real performance hit.
        String firstPortletName =
                this.aggregatedPortletLookupDao
                        .getMappedPortletForFname(form.getPortlets().iterator().next())
                        .getFname();
        Long firstGroupId = form.getGroups().iterator().next().longValue();
        String firstGroupName =
                this.aggregatedGroupLookupDao.getGroupMapping(firstGroupId).getGroupName();
        String firstExecutionType = form.getExecutionTypeNames().iterator().next();

        TitleAndCount[] items =
                new TitleAndCount[] {
                    new TitleAndCount(firstPortletName, portletSize),
                    new TitleAndCount(firstExecutionType, executionTypeSize),
                    new TitleAndCount(firstGroupName, groupSize)
                };

        return titleAndColumnDescriptionStrategy.getReportTitleAugmentation(items);
    }

    /**
     * Create column descriptions for the portlet report using the configured report labelling
     * strategy.
     *
     * @param reportColumnDiscriminator
     * @param form The original query form
     * @return
     */
    @Override
    protected List<ColumnDescription> getColumnDescriptions(
            PortletExecutionAggregationDiscriminator reportColumnDiscriminator,
            PortletExecutionReportForm form) {
        int groupSize = form.getGroups().size();
        int portletSize = form.getPortlets().size();
        int executionTypeSize = form.getExecutionTypeNames().size();

        String portletName = reportColumnDiscriminator.getPortletMapping().getFname();
        String groupName = reportColumnDiscriminator.getAggregatedGroup().getGroupName();
        String executionTypeName = reportColumnDiscriminator.getExecutionType().getName();

        TitleAndCount[] items =
                new TitleAndCount[] {
                    new TitleAndCount(portletName, portletSize),
                    new TitleAndCount(executionTypeName, executionTypeSize),
                    new TitleAndCount(groupName, groupSize)
                };

        return titleAndColumnDescriptionStrategy.getColumnDescriptions(
                items, showFullColumnHeaderDescriptions(form), form);
    }

    @Override
    protected List<Value> createRowValues(
            PortletExecutionAggregation aggr, PortletExecutionReportForm form) {
        int count = aggr != null ? aggr.getExecutionCount() : 0;
        return Collections.<Value>singletonList(new NumberValue(count));
    }
}
